use std::{
    hash::Hasher,
    ops::Deref,
    sync::{Arc, RwLock},
};

use wgpu::{util::DeviceExt, Color, PrimitiveState};
use winit::raw_window_handle::HasWindowHandle;

use crate::render::arc::{ArcBuffer, ArcShaderModule};

use self::arc::ArcRenderPipeline;

pub mod arc;
pub mod group;

pub mod buffer;
// pub mod program;

pub struct WgpuStateInner {
    pub instance: wgpu::Instance,
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
    pub surface: RwLock<(wgpu::Surface, wgpu::SurfaceConfiguration)>,
    pub size: WindowSize,
}

#[derive(Copy, Clone)]
pub struct WindowSize {
    pub width: u32,
    pub height: u32,
    pub scale_factor: f32,
}

/// Converts engine's optional texture "pointer" to fyrox-ui's.
// pub fn into_gui_texture(this: TextureResource) -> draw::SharedTexture {
//     draw::SharedTexture(this.into_untyped().0)
// }

/// "Transmutes" array of any sized type to a slice of bytes.
pub fn array_as_u8_slice<T: Sized>(v: &[T]) -> &'_ [u8] {
    // SAFETY: It is safe to reinterpret data to read it.
    unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, std::mem::size_of_val(v)) }
}

/// "Transmutes" value of any sized type to a slice of bytes.
pub fn value_as_u8_slice<T: Sized>(v: &T) -> &'_ [u8] {
    // SAFETY: It is safe to reinterpret data to read it.
    unsafe { std::slice::from_raw_parts(v as *const T as *const u8, std::mem::size_of::<T>()) }
}

/// Takes a vector of trivially-copyable values and turns it into a vector of bytes.
pub fn transmute_vec_as_bytes<T: Copy>(vec: Vec<T>) -> Vec<u8> {
    unsafe {
        let mut vec = std::mem::ManuallyDrop::new(vec);
        Vec::from_raw_parts(
            vec.as_mut_ptr() as *mut u8,
            vec.len() * std::mem::size_of::<T>(),
            vec.capacity() * std::mem::size_of::<T>(),
        )
    }
}

/// Performs hashing of a sized value by interpreting it as raw memory.
pub fn hash_as_bytes<T: Sized, H: Hasher>(value: &T, hasher: &mut H) {
    hasher.write(value_as_u8_slice(value))
}

pub struct WgpuState(Arc<WgpuStateInner>);

impl Deref for WgpuState {
    type Target = WgpuStateInner;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Clone for WgpuState {
    fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}

#[derive(Debug)]
pub enum RendererError {
    GraphicsInitializationError,
    RenderError(String),
    NoSuitableGPUAdaptersFoundOnTheSystem,
}

///
/// 图形渲染器，提供一些对于底层 WGPU 的操作的封装 
/// 
pub struct Renderer {
    state: WgpuState,
    pipeline: ArcRenderPipeline,
    vertex_buffer: ArcBuffer,
    index_buffer: ArcBuffer,
}

impl std::fmt::Debug for Renderer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Renderer")
            .field("version", &"1.0.0")
            .finish()
    }
}

impl Renderer {
    #[allow(unsafe_code)]
    pub fn new(window: &winit::window::Window, vsync: bool) -> Result<Self, RendererError> {

        let backends = wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::PRIMARY);
        let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default();
        let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default();

        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
            backends,
            flags: wgpu::InstanceFlags::from_build_config().with_env(),
            dx12_shader_compiler,
            gles_minor_version,
        });



        Self::new_from_instance(instance, window, vsync)
    }

    #[allow(unsafe_code)]
    fn new_from_instance(
        instance: wgpu::Instance,
        window: &winit::window::Window,

        vsync: bool,
    ) -> Result<Self, RendererError> {
        let surface = unsafe { instance.create_surface(window).unwrap() };

        // let adapter = instance
        //     .enumerate_adapters(wgpu::Backends::all())
        //     .filter(|adapter| {
        //         // 检查该适配器是否兼容我们的展示平面
        //         adapter.is_surface_supported(&surface)
        //     })
        //     .next()
        //     .ok_or(RendererError::GraphicsInitializationError)?;
        let adapter = pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default(
            &instance,
            Some(&surface)
        ))
        .ok_or(RendererError::NoSuitableGPUAdaptersFoundOnTheSystem)?;
        //  .expect("No suitable GPU adapters found on the system!");

        let adapter_info = adapter.get_info();
        log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
        // One instance is 96 bytes, and we allow 1 million of them, for a total of 96MB (default being 128MB).
        const MAX_INSTANCES: u32 = 1_000_000;
        const INSTANCE_BUFFER_SIZE: u32 = 96 * MAX_INSTANCES;

        let (device, queue) = pollster::block_on(adapter.request_device(
            &wgpu::DeviceDescriptor {
                label: None,
                features: wgpu::Features::default(),
                limits: wgpu::Limits {
                    // 1st: DrawParams
                    // 2nd: Texture + Sampler
                    // 3rd: InstanceArray
                    // 4th: ShaderParams
                    max_bind_groups: 4,
                    // InstanceArray uses 2 storage buffers.
                    max_storage_buffers_per_shader_stage: 2,
                    max_storage_buffer_binding_size: INSTANCE_BUFFER_SIZE,
                    max_texture_dimension_1d: 8192,
                    max_texture_dimension_2d: 8192,
                    ..wgpu::Limits::downlevel_webgl2_defaults()
                },
            },
            None,
        ))
        .unwrap();
        let size = window.inner_size();
        let scale_factor = window.scale_factor();

        let surface_config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: wgpu::TextureFormat::Bgra8UnormSrgb,
            width: size.width,
            height: size.height,
            present_mode: if vsync {
                wgpu::PresentMode::Fifo
            } else {
                wgpu::PresentMode::Mailbox
            },
            alpha_mode: wgpu::CompositeAlphaMode::Auto,
            view_formats: vec![wgpu::TextureFormat::Bgra8UnormSrgb],
        };
        surface.configure(&device, &surface_config);
        let state = WgpuState(Arc::new(WgpuStateInner {
            instance,
            surface: RwLock::new((surface, surface_config)),
            device,
            queue,
            size: WindowSize {
                width: size.width,
                height: size.height,
                scale_factor: scale_factor as f32,
            },
        }));

        #[repr(C)]
        #[derive(Copy, Clone, Debug)]
        struct Vertex {
            position: [f32; 3],
            uv: [f32; 2],
        }
        // lib.rs
        const VERTICES: &[Vertex] = &[
            Vertex {
                position: [-0.5, -0.5, 0.0],
                uv: [0.0, 0.0],
            },
            Vertex {
                position: [0.5, -0.5, 0.0],
                uv: [1.0, 0.0],
            },
            Vertex {
                position: [0.5, 0.5, 0.0],
                uv: [1.0, 1.0],
            },
            Vertex {
                position: [-0.5, 0.5, 0.0],
                uv: [0.0, 1.0],
            },
        ];

        // new()

        let vertex_buffer = ArcBuffer::new(state.device.create_buffer_init(
            &wgpu::util::BufferInitDescriptor {
                label: Some("Vertex Buffer"),
                contents: array_as_u8_slice(&VERTICES),
                usage: wgpu::BufferUsages::VERTEX,
            },
        ));

        let index_buffer = ArcBuffer::new(state.device.create_buffer_init(
            &wgpu::util::BufferInitDescriptor {
                label: Some("test"),
                contents: array_as_u8_slice(&[0, 1, 2, 2, 3, 0]),
                usage: wgpu::BufferUsages::INDEX,
            },
        ));

        let vertex_module = ArcShaderModule::new(state.device.create_shader_module(
            wgpu::ShaderModuleDescriptor {
                label: Some("vertex"),
                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
                    r#"
                    struct VertexOutput {
                        @builtin(position) position: vec4<f32>,
                        @location(0) uv: vec2<f32>, 
                        
                    };

                    struct VertexInput {
                        @location(0) position: vec3<f32>, 
                        @location(1) uv: vec2<f32>, 
                    
                    }

                    @vertex
                    fn main(input:VertexInput) -> VertexOutput {     
                        return VertexOutput(vec4<f32>(input.position, 1.0),input.uv);
                    }
                
                "#,
                )),
            },
        ));

        let fragment_module = ArcShaderModule::new(state.device.create_shader_module(
            wgpu::ShaderModuleDescriptor {
                label: Some("fragment"),
                source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
                    r#"
                 
                   

                    struct VertexOutput {
                        @builtin(position) position: vec4<f32>,
                        @location(0) uv:vec2<f32>
                    };
                    @fragment
                    fn main(input:VertexOutput) -> @location(0) vec4<f32> {
                        return vec4<f32>(1.0,0.0,1.0,1.0);
                    }
                
                "#,
                )),
            },
        ));
        let layout = state
            .device
            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Layout"),
                bind_group_layouts: &[],
                push_constant_ranges: &[],
            });
        let pipeline = ArcRenderPipeline::new(state.device.create_render_pipeline(
            &wgpu::RenderPipelineDescriptor {
                label: Some("Pipeline"),
                layout: Some(&layout),
                vertex: wgpu::VertexState {
                    module: &vertex_module,
                    entry_point: "main",
                    buffers: &[wgpu::VertexBufferLayout {
                        array_stride: 20,
                        step_mode: wgpu::VertexStepMode::Vertex,
                        attributes: &[
                            wgpu::VertexAttribute {
                                format: wgpu::VertexFormat::Float32x3,
                                offset: 0,
                                shader_location: 0,
                            },
                            wgpu::VertexAttribute {
                                format: wgpu::VertexFormat::Float32x2,
                                offset: 8,
                                shader_location: 1,
                            },
                        ],
                    }],
                },
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleStrip,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: None,
                    unclipped_depth: false,
                    polygon_mode: wgpu::PolygonMode::Fill,
                    conservative: false,
                },

                depth_stencil: None,
                multisample: wgpu::MultisampleState {
                    count: 1,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },

                fragment: Some(wgpu::FragmentState {
                    module: &fragment_module,
                    entry_point: "main",
                    targets: &[Some(wgpu::ColorTargetState {
                        format: wgpu::TextureFormat::Bgra8UnormSrgb,
                        blend: None,
                        write_mask: wgpu::ColorWrites::ALL,
                    })],
                }),
                multiview: None,
            },
        ));

        Ok(Self {
            state,
            pipeline,
            vertex_buffer,
            index_buffer,
        })
    }

    pub fn draw(&mut self) -> Result<(), RendererError> {
        let size = &self.state.size;
        let frame = {
            let surface_state = self.state.surface.write().unwrap();

            let surface = &surface_state.0;
            let mut surface_config = surface_state.1.clone();

            match surface.get_current_texture() {
                Ok(frame) => Ok(frame),
                Err(_) => {
                    surface_config.width = size.width.max(1);
                    surface_config.height = size.height.max(1);

                    surface.configure(&self.state.device, &surface_config);
                    surface.get_current_texture().map_err(|_| {
                        RendererError::RenderError(String::from(
                            "failed to get next swapchain image",
                        ))
                    })
                }
            }?
        };

        let surface_view = frame.texture.create_view(&Default::default());

        let mut cmd = self
            .state
            .device
            .create_command_encoder(&Default::default());

        {
            let mut pass = cmd.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &surface_view,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(Color {
                            r: 0.1,
                            g: 0.1,
                            b: 0.1,
                            a: 1.0,
                        }),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            });
            pass.set_pipeline(&self.pipeline);
            pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
            pass.draw_indexed(0..6, 0, 0..1);
        }

        self.state.queue.submit([cmd.finish()]);

        frame.present();

        Ok(())
    }
}
